Redirect Users & Show Error Pages
Learn to redirect users and show custom error pages in Python web development. This lesson covers core concepts, step-by-step examples, and troubleshooting tips.
Focus: redirect users and show error pages
You know the feeling: a user clicks a link that’s stale, submits a form with bad data, or lands on a page that no longer exists. Without proper redirects and error pages, they hit a dead end—and worse, they see a raw stack trace or a generic “404 Not Found” that erodes trust in your app. In this lesson, you’ll learn how to redirect users and show error pages in Python web development, turning awkward dead ends into smooth, user-friendly detours. By the end, you’ll be able to guide users where they need to go and handle failures gracefully—skills every production app demands.
The Problem This Lesson Solves
Every web app eventually faces a broken link, a moved resource, or an unauthorized attempt. If you ignore these cases, users get confusing error messages or blank screens, while search engines crawl dead ends and lower your site’s credibility. Worse, unhandled exceptions can leak sensitive stack traces—a security nightmare.
The pain is real: a 2019 study found that 88% of users are less likely to return after a bad experience. A single broken redirect can make visitors bounce, hurt conversion, and degrade your SEO ranking. But with a few lines of Python, you can turn every dead end into a helpful signpost.
This lesson tackles two sides of the same coin: redirecting users (when a resource moves or an action requires a fresh URL) and showing error pages (when something goes wrong, from 404 to 500). Together, they create a resilient web experience that keeps users engaged and developers calm.
Core Concept / Mental Model
Think of your web app as a train station with multiple platforms. Redirects are the signs that guide passengers from one platform to another—when a train’s route changes, you don’t just leave them standing; you point them to the new track. Error pages are the friendly station staff who calmly explain what happened when a passenger boards the wrong train or the line is canceled—they don’t disappear, they give clear instructions.
HTTP Status Codes: The Language of Redirects and Errors
- 3xx – Redirection: These codes tell the browser (or API client) that the resource lives elsewhere. The most common are 301 Moved Permanently (SEO-friendly permanent move) and 302 Found (temporary redirect, often after form submissions).
- 4xx – Client Error: The user’s request is malformed or forbidden. 404 Not Found means the URL doesn’t exist; 403 Forbidden means no access; 400 Bad Request for bad syntax.
- 5xx – Server Error: The server failed to fulfill a valid request. 500 Internal Server Error is the catch-all, but you can define custom pages for 503 (service unavailable) or 502 (bad gateway).
Redirect vs. Error: The Decision Tree
- Redirect when the resource exists but at a different URL (e.g., removed a blog post and sent users to a newer one).
- Show an error page when the resource doesn’t exist, access is denied, or the server crashed.
- Redirect after POST to prevent duplicate form submissions (Post/Redirect/Get pattern).
Pro Tip: Always use permanent redirects (301) for moved content that will never return—it preserves SEO equity. Temporary redirects (302) are ideal for a login flow or shopping cart.
How It Works Step by Step
Let’s trace what happens when a user requests a URL that needs a redirect or an error page. The process is consistent across Python web frameworks (Flask, Django, FastAPI), so once you grasp the pattern, you can apply it anywhere.
- Request arrives – The web server (e.g., Gunicorn) receives an HTTP request and passes it to your app’s routing layer.
- Router matches URL – The framework tries to match the path to a registered route. If no match, a
404error is triggered. - Redirect logic – If the route exists but the resource has moved, your code returns a
RedirectResponse(orredirect()helper) with a 301 or 302 status and aLocationheader pointing to the new URL. - Error handler triggers – If an unhandled exception occurs inside a view, the framework catches it and invokes your custom error handler for the relevant status code.
- Response sent – The browser follows the redirect automatically (for 3xx) or displays your error page template with the status code.
Key definitions: - Redirect – An HTTP response that tells the client to make a new request to a different URL. - Error page – A user-facing template that explains the problem and suggests next steps, returned with the appropriate 4xx/5xx status. - Error handler – A function that receives an exception (or status code) and returns a fallback response.
Why This Matters for Users and SEO
- Redirects consolidate page rank and avoid broken bookmarks.
- Custom error pages reduce bounce rates by guiding users back to helpful content.
- Clear status codes help search engines crawl correctly and developers debug faster.
Hands-on Walkthrough
We’ll use Flask for these examples because it’s minimal and beginner-friendly, but the same logic applies to Django or FastAPI. First, ensure you have Flask installed: pip install flask.
Example 1: Basic Redirect with redirect()
from flask import Flask, redirect, url_for, render_template
app = Flask(__name__)
@app.route('/')
def home():
return "Welcome to the new home page!"
# Old URL that now redirects to the new home
@app.route('/old-home')
def old_home():
return redirect(url_for('home')) # Default is 302
# Permanent redirect for moved content
@app.route('/old-blog/post-1')
def old_post():
return redirect('/blog/post-1', code=301)
if __name__ == '__main__':
app.run(debug=True)
Expected output (visit /old-home in your browser):
- The server logs a 302 status, and your browser automatically navigates to /.
- For /old-blog/post-1, you’ll see a 301 in the network tab, and the URL changes permanently.
Example 2: Custom Error Pages
Now let’s create a friendly 404 page and a generic 500 error page.
from flask import Flask, render_template, request
app = Flask(__name__)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html', path=request.path), 404
@app.errorhandler(500)
def internal_error(e):
return render_template('500.html'), 500
# A route that deliberately raises an error to test the 500 page
@app.route('/break')
def break_it():
raise ValueError("Something broke!")
if __name__ == '__main__':
app.run(debug=True)
Create templates/404.html:
<!DOCTYPE html>
<html>
<head><title>Page Not Found</title></head>
<body>
<h1>Oops! Page not found</h1>
<p>We couldn't find <code>{{ path }}</code>. Maybe it moved?</p>
<a href="/">Go back home</a>
</body>
</html>
And templates/500.html:
<!DOCTYPE html>
<html>
<head><title>Server Error</title></head>
<body>
<h1>Something went wrong</h1>
<p>We're working on it. Please try again later.</p>
</body>
</html>
Visit /nonexistent → you’ll see your custom 404 page with the path. Visit /break → you’ll see the 500 page (in production, not the debugger).
Example 3: Redirect After Form Submission (Post/Redirect/Get)
This pattern prevents duplicate form submissions when a user refreshes.
from flask import Flask, request, redirect, url_for, flash
app = Flask(__name__)
app.secret_key = 'secret'
@app.route('/submit', methods=['POST'])
def submit_form():
# Save data to database...
flash("Your form was submitted successfully!")
return redirect(url_for('success')) # 302
@app.route('/success')
def success():
return "Success! You won't see this by refreshing after a POST."
Why it works: After the POST, the server responds with a 302 redirect to a GET route. The browser makes a new GET request, so refreshing that page doesn’t resubmit the form.
Pro Tip: Always test your redirects and error handlers in production mode (set
debug=False). The Flask debugger can leak stack traces, which is fine in dev but dangerous in production.
Compare Options / When to Choose What
The two frameworks you’ll most likely master are Flask and Django, but FastAPI is also popular. Here’s how redirects and error handling differ:
| Framework | Redirect method | Custom error registration | Best for |
|---|---|---|---|
| Flask | redirect() helper |
@app.errorhandler(404) decorator |
Small to medium apps, microservices, learning |
| Django | redirect() from django.shortcuts |
Custom handler404, handler500 in URLconf |
Full-featured monoliths, admin heavy apps |
| FastAPI | RedirectResponse() object |
Register exception handlers with @app.exception_handler(404) |
High-performance async APIs |
When to choose what:
- Redirect with 301 – When you move a page permanently (e.g., change URL structure).
- Redirect with 302 – For temporary moves, login flows, or after POST.
- Custom error pages – Always, for every 4xx and 5xx that users can hit.
Variations worth knowing:
- Middleware-based error handling – Some frameworks let you wrap the app in middleware to catch all unhandled errors globally.
- Error logging services – Integrate Sentry or logging to track 500s in production—better than relying on custom pages alone.
- Async error handlers – FastAPI supports async error handlers for non-blocking templates.
Troubleshooting & Edge Cases
Even seasoned developers hit snags. Here are common issues and their fixes:
1. Redirect loop (302 infinitely)
- Symptom: Browser says “This page isn’t working. Redirect loop.”
- Cause: You’re redirecting to a URL that also redirects back.
- Fix: Check the
Locationheader chain; ensure at least one route ends with a 200 response.
2. Error page not showing, raw stack trace appears
- Symptom: You see a debug traceback even after adding error handlers.
- Cause:
debug=Trueis on, or your error handler is registered for the wrong code. - Fix: Set
debug=Falsein production, and verify you’re using@app.errorhandler(404)not@app.errorhandler(Exception)(the latter catches everything but returns 500).
3. 404 for valid routes after changing URL structure
- Symptom: Old bookmarks break.
- Fix: Implement 301 redirects from the old URLs to new ones, and update internal links.
4. Error handler returns 200 status
- Symptom: Search engines index your error page as a real page.
- Fix: Always return the correct status code:
return render_template('404.html'), 404.
5. POST data lost after redirect
- Symptom: Flash message missing because you redirected to a new route.
- Fix: Use
url_forto generate the URL and pass data in query parameters or session—never rely on POST data after redirect.
Pro Tip: Use a tool like
curl -Ito inspect redirects:curl -I http://localhost:5000/old-homewill showLocation: /and the status code.
What You Learned & What’s Next
You now understand how to redirect users and show error pages in Python web development. You learned: - The importance of handling dead ends gracefully for user trust and SEO. - How HTTP status codes (3xx, 4xx, 5xx) drive redirects and error pages. - The step-by-step flow of a request that triggers a redirect or error handler. - Practical Flask examples for redirects, custom 404/500 pages, and Post/Redirect/Get. - How to compare frameworks and choose the right redirect type. - How to troubleshoot loops, status codes, and debug-mode pitfalls.
These skills are the backbone of a resilient web app—every production app needs them. Next in the track, you’ll likely dive into managing user sessions and authentication, where redirects become essential for protecting routes. You’ll build on this foundation to create login flows and authorization checks.
Now, pick your favorite framework and implement custom error pages and redirects for an existing app. You’ll feel the difference immediately in user experience and robustness.
Practice recap
Take the Flask app you built in the walkthrough and add two more error handlers: one for 403 (Forbidden) and one for 503 (Service Unavailable). Then, add a redirect from an old URL to a new one using 301. Finally, use curl -I to verify the status codes and locations.
Common mistakes
- Using 302 for permanent moves, which dilutes SEO; always use 301 for content that will never return.
- Forgetting to return the correct status code in error handlers, causing search engines to index error pages as real content.
- Setting debug=True in production, leaking stack traces; always disable debug and use logging for diagnostics.
- Creating a redirect loop by redirecting to a URL that itself redirects back—always test the final destination.
Variations
- Django uses
redirect()fromdjango.shortcutsand customhandler404/handler500in your URLconf. - FastAPI returns
RedirectResponse()directly and registers exception handlers with@app.exception_handler(404). - You can implement global error handling via middleware or WSGI/ASGI hooks to catch unhandled exceptions across your app.
Real-world use cases
- E-commerce site redirecting /old-product to a new URL after a product rename, preserving page rank.
- SaaS app redirecting unauthenticated users to a login page, then back to their original destination after login.
- Blog platform showing a custom 404 page with suggestions for popular posts to keep users on-site.
Key takeaways
- Redirects (3xx) point users to correct URLs, while error pages (4xx/5xx) handle failures gracefully.
- Use 301 permanently, 302 temporarily, and always after form POST to prevent duplicate submissions.
- Custom error pages with correct status codes improve user experience and protect against information leaks.
- Test redirects and errors in production mode (debug=False) to avoid exposing stack traces.
- Always return the proper HTTP status code from your error handler to maintain SEO integrity.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.