renderRedirect Service
The renderRedirect
service provides a declarative way to perform redirects in Pinstripe applications using custom web components. It generates pinstripe-redirect
tags that are processed by the client-side framework to perform navigation.
Interface
The service creates a function that accepts an attributes object:
this.renderRedirect(attributes)
Parameters
attributes
(object, optional) - Configuration object containing redirect parameters. Defaults to{}
.
Common Attributes
url
(string) - The URL to redirect to. Can be relative (/dashboard
) or absolute (https://example.com
)target
(string) - The redirect target context:'_top'
- Redirect the entire page'_parent'
- Redirect the parent frame/overlay'_overlay'
- Redirect within overlay context
Return Value
Returns an Html
instance containing a <pinstripe-redirect>
tag that:
- Can be converted to an HTTP response with
toResponseArray()
- Integrates with the Pinstripe client-side navigation system
- Triggers redirect behavior when rendered in the browser
Description
The renderRedirect
service is built on top of the renderTag
service and generates pinstripe-redirect
custom elements that:
- Provides declarative redirects without requiring imperative JavaScript navigation
- Supports multiple redirect contexts through the
target
attribute - Integrates with overlay systems for context-aware navigation
- Works with form submissions to redirect after successful operations
- Handles client-server synchronization by working in both environments
- Enables conditional redirects based on authentication, permissions, or business logic
The service is commonly used in form success handlers, authentication flows, and action responses where navigation needs to occur after processing.
Examples
Basic URL Redirect
// Simple redirect to a path
export default {
async render() {
if (await this.isSignedOut) {
return this.renderRedirect({ url: '/login' });
}
return this.renderView('dashboard');
}
}
Page-Level Redirects
// Redirect the entire page (most common)
export default {
async render() {
await this.processUserLogout();
return this.renderRedirect({ target: '_top' });
}
}
Overlay Context Redirects
// Redirect within an overlay/modal context
export default {
async render() {
const user = await this.database.users.insert(this.params);
return this.renderRedirect({
url: `/users/${user.id}`,
target: '_parent'
});
}
}
Form Success Redirects
// Redirect after successful form submission
export default {
render() {
return this.renderForm(this.database.posts, {
fields: ['title', 'content'],
success: ({ id }) => {
return this.renderRedirect({
url: `/posts/${id}`,
target: '_top'
});
}
});
}
}
Conditional Authentication Redirects
// Redirect based on authentication state
export default {
async render() {
if (await this.isSignedOut) {
const returnUrl = encodeURIComponent(this.params._url.pathname);
return this.renderRedirect({
url: `/_actions/guest/sign_in?returnUrl=${returnUrl}`
});
}
return this.renderView('protected-content');
}
}
HTTP Response Integration
// Convert redirect to HTTP response with custom headers
export default {
async render() {
const session = await this.createUserSession();
const [status, headers, body] = this.renderRedirect({
target: '_top'
}).toResponseArray();
// Add session cookie
headers['Set-Cookie'] = `pinstripeSession=${session.id}:${session.passString}`;
return [status, headers, body];
}
}
Dynamic URL Construction
// Build redirect URLs dynamically
export default {
async render() {
const { action, id } = this.params;
if (action === 'delete') {
await this.database.posts.where({ id }).delete();
return this.renderRedirect({ url: '/posts' });
}
if (action === 'publish') {
await this.database.posts.where({ id }).update({ published: true });
return this.renderRedirect({ url: `/posts/${id}` });
}
return this.renderView('post-actions', { id });
}
}
Multi-Step Form Workflows
// Navigate through multi-step processes
export default {
async render() {
const { step, data } = this.params;
if (step === '1') {
// Process step 1, redirect to step 2
await this.saveStepOneData(data);
return this.renderRedirect({ url: '/wizard?step=2' });
}
if (step === '2') {
// Process step 2, redirect to completion
await this.saveStepTwoData(data);
return this.renderRedirect({ url: '/wizard/complete' });
}
return this.renderView('wizard-step', { step });
}
}
Permission-Based Redirects
// Redirect based on user permissions
export default {
async render() {
const user = await this.user;
if (!user) {
return this.renderRedirect({ url: '/login' });
}
if (user.role !== 'admin') {
return this.renderRedirect({ url: '/unauthorized' });
}
return this.renderView('admin-panel');
}
}
Nested Redirect Logic
// Complex redirect logic with multiple conditions
export default {
async render() {
const user = await this.user;
const { commentableId } = this.params;
if (!user) {
const loginUrl = `/_actions/guest/sign_in?title=${encodeURIComponent('Add comment')}&returnUrl=${encodeURIComponent(`/_actions/guest/add_comment?commentableId=${commentableId}`)}`;
return this.renderRedirect({ url: loginUrl });
}
// Process comment creation...
const comment = await this.database.comments.insert({
commentableId,
userId: user.id,
body: this.params.body
});
// Redirect back to the content
return this.renderRedirect({ target: '_top' });
}
}
Client-Side Script Integration
// Combine redirects with client-side behavior
export default {
async render() {
const returnUrl = this.params.returnUrl;
if (returnUrl) {
return this.renderHtml`
${this.renderRedirect({ url: returnUrl })}
<script type="pinstripe">
const { document } = this;
this.overlay.on('close', () => document.load());
</script>
`;
}
return this.renderRedirect({ target: '_top' });
}
}
Target Behaviors
target: '_top'
- Redirects the entire browser window/tab
- Most common for post-authentication redirects
- Used when leaving the current application context
- Triggers full page navigation
target: '_parent'
- Redirects the parent frame or overlay
- Used within iframes or embedded contexts
- Maintains overlay/modal workflows
- Preserves parent page state
No Target (Default)
- Context-dependent redirect behavior
- Usually behaves like
_top
for standalone pages - May behave like
_parent
in overlay contexts
Common Use Cases
- Authentication Flows: Redirect after login/logout
- Form Success: Navigate after successful submissions
- Access Control: Redirect unauthorized users
- Workflow Steps: Move between process stages
- Error Handling: Redirect on validation failures
- Admin Actions: Navigate after CRUD operations
Integration Patterns
With Form Validation
export default {
render() {
return this.renderForm(this.database.users, {
fields: ['email', 'password'],
validateWith: async ({ email }) => {
const existing = await this.database.users.where({ email }).first();
if (existing) throw new Error('Email already exists');
},
success: ({ id }) => this.renderRedirect({ url: `/users/${id}` })
});
}
}
With Session Management
export default {
async render() {
if (await this.session) {
await this.session.delete();
}
const [status, headers, body] = this.renderRedirect({
target: '_top'
}).toResponseArray(200);
headers['Set-Cookie'] = 'pinstripeSession=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
return [status, headers, body];
}
}
With Error Handling
export default {
async render() {
try {
await this.processPayment(this.params);
return this.renderRedirect({ url: '/payment/success' });
} catch (error) {
return this.renderRedirect({
url: `/payment/error?message=${encodeURIComponent(error.message)}`
});
}
}
}
Response Integration
The service integrates seamlessly with HTTP responses:
// Basic response conversion
const [status, headers, body] = this.renderRedirect({ url: '/dashboard' }).toResponseArray();
// Custom status codes
const [status, headers, body] = this.renderRedirect({ url: '/error' }).toResponseArray(302);
// Adding custom headers
const response = this.renderRedirect({ target: '_top' }).toResponseArray();
response[1]['X-Custom-Header'] = 'value';
return response;
Best Practices
URL Construction
- Use relative URLs (
/path
) for internal navigation - Encode query parameters with
encodeURIComponent()
- Build URLs dynamically using template literals
- Validate URLs before redirecting when using user input
Target Selection
- Use
target: '_top'
for complete page changes - Use
target: '_parent'
for overlay/modal workflows - Avoid target when context doesn't matter
Performance
- Combine redirects with necessary HTTP headers in one response
- Use redirects instead of client-side navigation for SEO benefits
- Cache redirect responses appropriately
Security
- Validate redirect URLs to prevent open redirect vulnerabilities
- Sanitize user-provided redirect parameters
- Use relative URLs when possible to prevent external redirects
Error Handling
The service handles common edge cases:
// Graceful fallback for missing URLs
const redirectUrl = user.preferredDashboard || '/dashboard';
return this.renderRedirect({ url: redirectUrl });
// Error boundary redirects
try {
await this.performAction();
return this.renderRedirect({ url: '/success' });
} catch (error) {
console.error('Action failed:', error);
return this.renderRedirect({ url: '/error' });
}